package com.jack.payment.token.util;


import com.jack.payment.constant.enums.LoginTokenEnum;
import com.jack.payment.constant.enums.TimeEnum;
import com.jack.payment.entity.common.BusinessException;
import com.jack.payment.token.constant.OneTokenConfig;
import com.jack.payment.util.common.StringUtil;
import com.jack.payment.util.common.TimeUtil;
import com.jack.payment.util.redis.RedisUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Component
@Configuration
public class TokenUtil {
    private Logger log = LoggerFactory.getLogger(TokenUtil.class);
    // 普通登录密钥
    private static final String TOKEN_LOGIN = "jackPay:token:login:";
    // 系统自动登录令牌
    private static final String TOKEN_AUTH_LOGIN = "jackPay:token:auth:login:";

    @Autowired
    private OneTokenConfig oneTokenConfig;
    @Autowired
    private RedisUtil redisUtil;

    private static long REPLACETIME;
    private static long VALIDTIME;
    private static boolean isConfigToken;
    private static TimeEnum LONGTIMEENUM;
    private static TimeEnum SHORTTIMEENUM;

    @Bean
    public void initGetTokenConfig() throws BusinessException {
        tokenTimeEnum(oneTokenConfig);
    }

    public synchronized void tokenTimeEnum(OneTokenConfig oneTokenConfig) throws BusinessException {
        if (oneTokenConfig == null) {
            log.info("未能获取到有效配置token信息!");
            isConfigToken = true;
            return;
        }
        oneTokenConfig = oneTokenConfig;
        LONGTIMEENUM = TimeEnum.getTimeTypeEnum(oneTokenConfig.getLongType());
        SHORTTIMEENUM = TimeEnum.getTimeTypeEnum(oneTokenConfig.getShortType());
        REPLACETIME = TimeUtil.getTimeEnumSecode(oneTokenConfig.getReplaceTimeout(), TimeEnum.DAY);
        VALIDTIME = TimeUtil.getTimeEnumSecode(oneTokenConfig.getLongTimeout(), LONGTIMEENUM);
    }

    /** 将获取的token, 进行解析, **/
    private Claims getClaimFromToken(String token){
        return  Jwts.parser()
                .setSigningKey(oneTokenConfig.getAuthKey())
                .parseClaimsJws(token)
                .getBody();
    }



    /** 私有方法 生成客户端,所需的短token **/
    private String genrateShortToken(String client, String uuid, String user, String subject) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("user", user);
        claims.put("client", client);
        claims.put("refreshToken", uuid);
        Date createDate = new Date();
        Date expirationDate = new Date(TimeUtil.getCurrentTimeStamp(oneTokenConfig.getShortTimeout(), createDate, SHORTTIMEENUM));
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(createDate)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, oneTokenConfig.getAuthKey())
                .compact();
    }

    /** 验证token 是否合法!!!
     * **/
    private LoginTokenEnum verifyLoginTokenInfo(String token, String refreshToken,
                                                String client, String user){
        if (StringUtil.isEmpty(refreshToken)) {
            log.error("未能获取该客户端有效的token信息, {} 未能获取到长token {}", user, refreshToken);
            return LoginTokenEnum.LOGIN_INFO_ERROR;
        }
        // 解析请求时客户端携带的短token
        Claims claims = null;
        try {
            claims = getClaimFromToken(token);
        } catch (ExpiredJwtException e) {
            log.warn("token 信息已过期");
            claims = e.getClaims();
        }
        String tokenRefreshToken = claims.get("refreshToken").toString();
        if (!tokenRefreshToken.equals(refreshToken)) {
            log.warn("账户已在其他地方登录, 短token 值与 长token值不一致");
            return LoginTokenEnum.LOGIN_IN_REST_LOGIN;
        }
        if (claims.getExpiration().before(new Date())) {
            log.warn("token 信息短过期");
            return LoginTokenEnum.LOGIN_INFO_PASTDUE;
        }
        String tokenUser = claims.get("user").toString();
        if (StringUtil.isEmpty(user) || !user.equals(tokenUser)) {
            log.error("token 用户信息不一致");
            return LoginTokenEnum.LOGIN_INFO_ERROR;
        }
        String tokenClient = claims.get("client").toString();
        if (StringUtil.isEmpty(client) || !client.equals(tokenClient)) {
            log.error("token 信息客户端信息不符合");
            return LoginTokenEnum.LOGIN_INFO_ERROR;
        }
        return LoginTokenEnum.LOGIN_SUCCESS;
    }

    /**
     *  验证登录是否过期!!!
     *  1: 如果短token过期 且长token 存在 则给返回短token
     *  2: 如果长token 还差 3 天过期, 且短token 有效, 则 设置增加长token时间
     *  4: 两个token 同时失效!, 用户重新登录
     */
    public String isDeassignToken(String token, String client, String user) throws BusinessException {
        log.info("获取redis key 值为{}", TOKEN_LOGIN + client + user);
        String refreshToekn = redisUtil.get(TOKEN_LOGIN + client + user);
        // 长token 与 客户端携带的token 进行校验
        LoginTokenEnum loginTokenEnum = verifyLoginTokenInfo(token, refreshToekn, client, user);
        // 短token 是否为
        if (loginTokenEnum == LoginTokenEnum.LOGIN_INFO_PASTDUE) {
            long validTime = redisUtil.getExpireTime(TOKEN_LOGIN + client + user);
            if (REPLACETIME >= validTime ) {
                log.info("{} 用户 {} 的长token在指定时间内登录,将进行重设长token有效值", user, client);
                redisUtil.set(TOKEN_LOGIN + client + user, refreshToekn, VALIDTIME);
            }
            return genrateShortToken(client, refreshToekn, user, "login");
        }
        if (loginTokenEnum == LoginTokenEnum.LOGIN_SUCCESS) {
            log.info("短token 未过期");
            return token;
        }
        throw new BusinessException(loginTokenEnum);
    }


    /**
     * 用户 生成有效token 信息
     * 1: 用户不能在其他地区登录
     */
    public String login(String client, String user, boolean verify) throws BusinessException {
        String getToken = redisUtil.get(TOKEN_LOGIN + client + user);
        if (StringUtil.isEmpty(getToken) || verify) {
            String refreshToekn = UUID.randomUUID().toString();
            redisUtil.set(TOKEN_LOGIN + client + user, refreshToekn, VALIDTIME);
            return genrateShortToken(client, refreshToekn, user, "login");
        } else {
            log.warn("服务器中已有此用户token, 用户 {}, 服务token{}", user, getToken);
            throw new BusinessException(LoginTokenEnum.LOGIN_INFO_PASTDUE);
        }
    }

    /** 用户退出登录 **/
    public LoginTokenEnum logout(String client, String user) throws BusinessException {
        try {
            redisUtil.delete(TOKEN_LOGIN + client + user);
            return LoginTokenEnum.LOGOUT_LOGIN_SUCCESS;
        } catch (Exception e) {
            log.error("用户退出登录失败!!!! redis 删除键值对失败!");
            throw new BusinessException(LoginTokenEnum.LOGOUT_LOGIN_FAIL);
        }
    }

    /** 用户注销 删除该用户的所有token信息  **/
    public void unsubscribeAccount(String userId) {
        redisUtil.delete(TOKEN_LOGIN + "Web" + userId);
    }

}
